From 6a9797b8af53485a7078240e5bf96b05b50fcf99 Mon Sep 17 00:00:00 2001 From: "tw275@labyrinth.cl.cam.ac.uk" Date: Thu, 22 Jul 2004 15:33:02 +0000 Subject: [PATCH] bitkeeper revision 1.1108.2.17 (40ffde2eqJ67r1igRaAha9mzkh_gLA) Removed async code from sv, and now used xend http interface for node info --- tools/python/xen/sv/DomInfo.py | 49 +-- tools/python/xen/sv/DomList.py | 79 +--- tools/python/xen/sv/GenTabbed.py | 24 +- tools/python/xen/sv/HTMLBase.py | 22 +- tools/python/xen/sv/Main.py | 30 +- tools/python/xen/sv/NodeInfo.py | 19 +- tools/python/xen/sv/util.py | 20 +- tools/python/xen/xend/XendClient.py | 644 ++++++++++++++++++---------- 8 files changed, 511 insertions(+), 376 deletions(-) diff --git a/tools/python/xen/sv/DomInfo.py b/tools/python/xen/sv/DomInfo.py index a26736011c..42abdee799 100755 --- a/tools/python/xen/sv/DomInfo.py +++ b/tools/python/xen/sv/DomInfo.py @@ -1,20 +1,20 @@ -from HTMLBase import HTMLBase -from XendClientDeferred import server +from xen.xend.XendClient import aserver as server from xen.xend import PrettyPrint +from xen.sv.HTMLBase import HTMLBase from xen.sv.util import * from xen.sv.GenTabbed import * class DomInfo( GenTabbed ): - def __init__( self, urlWriter, callback ): + def __init__( self, urlWriter ): self.dom = 0; def tabUrlWriter( tab ): return urlWriter( "mod=info&dom=%s%s" % ( self.dom, tab ) ) - GenTabbed.__init__( self, tabUrlWriter, [ 'General', 'SXP', 'Devices' ], [ DomGenTab, DomSXPTab, NullTab ], callback ) + GenTabbed.__init__( self, tabUrlWriter, [ 'General', 'SXP', 'Devices' ], [ DomGenTab, DomSXPTab, NullTab ] ) def write_BODY( self, request ): dom = request.args.get('dom') @@ -43,7 +43,8 @@ class DomGenTab( GeneralTab ): GeneralTab.__init__( self, "General Domain Info", {}, titles ) - def write_BODY( self, request, callback ): + def write_BODY( self, request ): + dom = request.args.get('dom') if dom is None or len(dom) != 1: @@ -52,14 +53,9 @@ class DomGenTab( GeneralTab ): else: self.dom = dom[0] - deferred = getDomInfoHash( self.dom ) - deferred.addCallback( self.continue_BODY, request, callback ) - - def continue_BODY( self, dict, request, callback ): - - self.dict = dict + self.dict = getDomInfoHash( self.dom ) - GeneralTab.write_BODY( self, request, callback ) + GeneralTab.write_BODY( self, request ) class DomSXPTab( PreTab ): @@ -67,21 +63,8 @@ class DomSXPTab( PreTab ): self.dom = 0 PreTab.__init__( self, "" ) - def fn( self, x, request ): - class tmp: - def __init__( self ): - self.str = "" - def write( self, str ): - self.str = self.str + str - temp = tmp() - PrettyPrint.prettyprint( x, out=temp ) - self.source = temp.str - return request - - def fn2( self, request, callback ): - PreTab.write_BODY( self, request, callback ) - - def write_BODY( self, request, callback ): + + def write_BODY( self, request ): dom = request.args.get('dom') if dom is None or len(dom) != 1: @@ -90,10 +73,10 @@ class DomSXPTab( PreTab ): else: self.dom = dom[0] - deferred = server.xend_domain( self.dom ) + domInfo = server.xend_domain( self.dom ) + + self.source = sxp2string( domInfo ) - deferred.addCallback( self.fn, request ) - deferred.addCallback( self.fn2, callback ) - def errback( x ): - print ">err ", x - deferred.addErrback( errback ) + PreTab.write_BODY( self, request ) + + diff --git a/tools/python/xen/sv/DomList.py b/tools/python/xen/sv/DomList.py index 37229b6941..ee55957ba6 100755 --- a/tools/python/xen/sv/DomList.py +++ b/tools/python/xen/sv/DomList.py @@ -1,93 +1,54 @@ -from twisted.web import resource -from twisted.web.server import NOT_DONE_YET - -from xen.xend.XendClient import server as XendServer +from xen.xend.XendClient import server from xen.xend import sxp from xen.sv.HTMLBase import HTMLBase from xen.sv.util import * -from twisted.internet import reactor - class DomList( HTMLBase ): isLeaf = True - def __init__( self, urlWriter, callback ): + def __init__( self, urlWriter ): HTMLBase.__init__(self) self.urlWriter = urlWriter - self.head = None - self.long = None - self.rendered_domains = {} - self.domCount = 0 - self.callback = callback def write_BODY( self, request, head=True, long=True ): - deferred = XendServer.xend_domains() - deferred.addCallback( self.get_domain_info, request ) - deferred.addErrback( self.errback ) - - self.head = head - self.long = long - - def errback( self, err ): - print 'errback>', err - - def get_domain_info( self, domains, request ): - - self.domCount = len( domains ) - for domain in domains: - deferred = getDomInfoHash( domain ) - deferred.addCallback( self.render_domain, request ) - deferred.addErrback( self.errback ) - - def render_domain( self, domInfoHash, request ): + domains = server.xend_domains() - domStr = "%(dom)-4d\n" % domInfoHash - - url = self.urlWriter( "mod=info&dom=%(dom)-4d" % domInfoHash ) - - domStr += "%s\n" % ( url, domInfoHash['name'] ) - - if self.long: - domStr += "%(mem)7d\n" % domInfoHash - domStr += "%(cpu)3d\n" % domInfoHash - - domStr += "%(state)5s\n" % domInfoHash - - if self.long: - domStr += "%(cpu_time)7.1f\n" % domInfoHash - - self.rendered_domains[ domInfoHash[ 'dom' ] ] = domStr - self.domCount -= 1 - - if self.domCount == 0: - self.finish_write_BODY( request ) - - def finish_write_BODY( self, request ): - request.write( "\n\n" ) - if self.head: + if head: request.write( "" ) - self.write_DOMAIN_HEAD( request, self.long ) + self.write_DOMAIN_HEAD( request, long ) request.write( "" ) odd = True - for domain in self.rendered_domains.values(): + + for domain in domains: if odd: request.write( "\n" ) odd = False else: request.write( "\n" ) odd = True - request.write( domain ) + self.write_DOMAIN( request, getDomInfoHash( domain ), long ) request.write( "\n" ) request.write( "
\n" ) + + def write_DOMAIN( self, request, domInfoHash, long=True ): + request.write( "%(dom)-4d\n" % domInfoHash ) - self.callback( request ) + url = self.urlWriter( "mod=info&dom=%(dom)-4d" % domInfoHash ) + + request.write( "%s\n" % ( url, domInfoHash['name'] ) ) + if long: + request.write( "%(mem)7d\n" % domInfoHash ) + request.write( "%(cpu)3d\n" % domInfoHash ) + request.write( "%(state)5s\n" % domInfoHash ) + if long: + request.write( "%(cpu_time)7.1f\n" % domInfoHash ) def write_DOMAIN_HEAD( self, request, long=True ): request.write( "Domain\n" ) diff --git a/tools/python/xen/sv/GenTabbed.py b/tools/python/xen/sv/GenTabbed.py index 2c0dd1283f..bfbe5b04cf 100755 --- a/tools/python/xen/sv/GenTabbed.py +++ b/tools/python/xen/sv/GenTabbed.py @@ -5,13 +5,12 @@ from xen.sv.TabView import TabView class GenTabbed( HTMLBase ): - def __init__( self, urlWriter, tabStrings, tabObjects, callback ): + def __init__( self, urlWriter, tabStrings, tabObjects ): HTMLBase.__init__(self) self.tab = 0; self.tabStrings = tabStrings self.tabObjects = tabObjects self.urlWriter = urlWriter - self.callback = callback def write_BODY( self, request, urlWriter = None ): tab = request.args.get('tab') @@ -34,29 +33,23 @@ class GenTabbed( HTMLBase ): request.write( "

Bad Tab

" ) self.finish_BODY( request ) else: - render_tab.write_BODY( request, self.finish_BODY ) + render_tab.write_BODY( request ) - def finish_BODY( self, request ): - request.write( "" ) - self.callback( request ) - class PreTab( HTMLBase ): def __init__( self, source ): HTMLBase.__init__( self ) self.source = source - def write_BODY( self, request, callback ): + def write_BODY( self, request ): request.write( "
" )
         
         request.write( self.source )
         
         request.write( "
" ) - - callback( request ) class GeneralTab( HTMLBase ): @@ -66,7 +59,7 @@ class GeneralTab( HTMLBase ): self.dict = dict self.titles = titles - def write_BODY( self, request, callback ): + def write_BODY( self, request ): request.write( "

%s

" % self.title ) @@ -87,17 +80,14 @@ class GeneralTab( HTMLBase ): writeAttr( niceName, attr ) request.write( "" ) - - callback( request ) - + class NullTab( HTMLBase ): def __init__( self ): HTMLBase.__init__( self ) self.title = "Null Tab" - def write_BODY( self, request, callback ): + def write_BODY( self, request ): request.write( "

%s

" % self.title ) - callback( request ) - + diff --git a/tools/python/xen/sv/HTMLBase.py b/tools/python/xen/sv/HTMLBase.py index e127dedac6..4479d7e455 100755 --- a/tools/python/xen/sv/HTMLBase.py +++ b/tools/python/xen/sv/HTMLBase.py @@ -1,27 +1,25 @@ -from twisted.web import server, resource -from twisted.internet import reactor +from twisted.web.resource import Resource + +class HTMLBase( Resource ): -class HTMLBase( resource.Resource ): - isLeaf = True - + def __init__( self ): - resource.Resource.__init__(self) - + Resource.__init__(self) + def render_GET( self, request ): self.write_TOP( request ) - return self.write_BODY( request, self.finish_render_GET ) - - def finish_render_GET( self, request ): + self.write_BODY( request ) self.write_BOTTOM( request ) request.finish() + return '' def write_BODY( self, request ): - request.write( "BODY" ) + request.write( "BODY" ) def write_TOP( self, request ): request.write( 'Xen' ) request.write( '' ) def write_BOTTOM( self, request ): - request.write( "" ) \ No newline at end of file + request.write( "" ) diff --git a/tools/python/xen/sv/Main.py b/tools/python/xen/sv/Main.py index 1bbc3f0bfd..6b9bbfae05 100755 --- a/tools/python/xen/sv/Main.py +++ b/tools/python/xen/sv/Main.py @@ -1,11 +1,4 @@ -from twisted.web import resource -from twisted.web.server import NOT_DONE_YET - -from xen.xend.XendClient import server as XendServer -from xen.xend import sxp - -from HTMLBase import HTMLBase - +from xen.sv.HTMLBase import HTMLBase from xen.sv import DomList, NodeInfo, DomInfo class Main( HTMLBase ): @@ -21,10 +14,8 @@ class Main( HTMLBase ): def mainUrlWriter( self, s ): return "Main.rpy?%s" % s - def write_BODY( self, request, callback ): + def write_BODY( self, request ): - self.callback = callback - request.write( "\n\n" ) request.write( "\n" ) request.write( " " ) @@ -37,11 +28,8 @@ class Main( HTMLBase ): request.write( "

Node details

" ) request.write( "

Domains summary

" ) - DomList.DomList( self.mainUrlWriter, self.continue_BODY ).write_BODY( request, True, False ) + DomList.DomList( self.mainUrlWriter ).write_BODY( request, True, False ) - return NOT_DONE_YET - - def continue_BODY( self, request ): request.write( " " ) request.write( "
 
" ) request.write( "  " ) @@ -56,18 +44,14 @@ class Main( HTMLBase ): if mod is None or len(mod) != 1: request.write( '

Please select a module

' ) - self.finish_BODY( request ) elif mod[0] == 'info': - DomInfo.DomInfo( self.mainUrlWriter, self.finish_BODY ).write_BODY( request ) + DomInfo.DomInfo( self.mainUrlWriter ).write_BODY( request ) elif mod[0] == 'list': - DomList.DomList( self.mainUrlWriter, self.finish_BODY ).write_BODY( request ) + DomList.DomList( self.mainUrlWriter ).write_BODY( request ) elif mod[0] == 'node': - NodeInfo.NodeInfo( self.mainUrlWriter, self.finish_BODY ).write_BODY( request ) + NodeInfo.NodeInfo( self.mainUrlWriter ).write_BODY( request ) else: request.write( '

Invalid module. Please select another

' ) - self.finish_BODY( request ) - - def finish_BODY( self, request ): request.write( " " ) request.write( " " ) @@ -77,5 +61,3 @@ class Main( HTMLBase ): request.write( "\n" ) - self.callback( request ) - diff --git a/tools/python/xen/sv/NodeInfo.py b/tools/python/xen/sv/NodeInfo.py index c0a569db58..68d050b3bb 100755 --- a/tools/python/xen/sv/NodeInfo.py +++ b/tools/python/xen/sv/NodeInfo.py @@ -1,24 +1,27 @@ -from xen.xend import XendDmesg -from xen.xend import XendNode +from xen.xend.XendClient import server from xen.sv.util import * from xen.sv.GenTabbed import * -from xen.sv.HTMLBase import HTMLBase class NodeInfo( GenTabbed ): - def __init__( self, urlWriter, callback ): + def __init__( self, urlWriter ): def newUrlWriter( url ): return urlWriter( "mod=node%s" % url ) - GenTabbed.__init__( self, newUrlWriter, [ 'General', 'Dmesg' ], [ NodeGeneralTab, NodeDmesgTab ], callback ) + GenTabbed.__init__( self, newUrlWriter, [ 'General', 'Dmesg' ], [ NodeGenTab, NodeDmesgTab ] ) +class NodeGenTab( PreTab ): + def __init__( self ): + text = sxp2string( server.xend_node() ) + PreTab.__init__( self, text ) + class NodeGeneralTab( GeneralTab ): def __init__( self ): - nodeInfo = XendNode.instance().info() + nodeInfo = server.xend_node() dictNodeInfo = {} @@ -42,6 +45,6 @@ class NodeGeneralTab( GeneralTab ): class NodeDmesgTab( PreTab ): def __init__( self ): - self.xd = XendDmesg.instance() - PreTab.__init__( self, self.xd.info()[0] ) + dmesg = server.xend_node_dmesg() + PreTab.__init__( self, dmesg[ 1 ] ) diff --git a/tools/python/xen/sv/util.py b/tools/python/xen/sv/util.py index 1d461c721b..47d3519011 100755 --- a/tools/python/xen/sv/util.py +++ b/tools/python/xen/sv/util.py @@ -1,12 +1,9 @@ from xen.xend.XendClient import server from xen.xend import sxp +from xen.xend import PrettyPrint def getDomInfoHash( domain ): - deferred = server.xend_domain( int( domain ) ) - deferred.addCallback( procDomInfo, domain ) - return deferred - -def procDomInfo( domInfo, domain ): + domInfo = server.xend_domain( int( domain ) ) d = {} d['dom'] = int( domain ) d['name'] = sxp.child_value( domInfo, 'name' ) @@ -19,7 +16,20 @@ def procDomInfo( domInfo, domain ): if( sxp.child_value( domInfo, 'start_time' ) ): d['start_time'] = float( sxp.child_value( domInfo, 'start_time' ) ) return d + +def sxp2hash( sxp ): + pass +def sxp2string( sxp ): + class tmp: + def __init__( self ): + self.str = "" + def write( self, str ): + self.str = self.str + str + temp = tmp() + PrettyPrint.prettyprint( sxp, out=temp ) + return temp.str + def bigTimeFormatter( time ): weeks = time // 604800 remainder = time % 604800 diff --git a/tools/python/xen/xend/XendClient.py b/tools/python/xen/xend/XendClient.py index 538f84c3a9..2d08f36056 100644 --- a/tools/python/xen/xend/XendClient.py +++ b/tools/python/xen/xend/XendClient.py @@ -1,6 +1,8 @@ +#!/usr/bin/env python # Copyright (C) 2004 Mike Wray """Client API for the HTTP interface on xend. Callable as a script - see main(). +Supports synchronous or asynchronous connection to xend. This API is the 'control-plane' for xend. The 'data-plane' is done separately. For example, consoles @@ -11,9 +13,12 @@ import sys import httplib import types from StringIO import StringIO -import urlparse + from twisted.protocols import http +from twisted.internet.protocol import ClientCreator +from twisted.internet.defer import Deferred +from twisted.internet import reactor from encode import * import sxp @@ -22,40 +27,22 @@ import PrettyPrint DEBUG = 0 class XendError(RuntimeError): - pass - -class Foo(httplib.HTTPResponse): - - def begin(self): - fin = self.fp - while(1): - buf = fin.readline() - print "***", buf - if buf == '': - print - sys.exit() - - -def sxprio(sxpr): - """Convert an sxpr to a string. + """Error class for 'expected errors' when talking to xend. """ - io = StringIO() - sxp.show(sxpr, out=io) - print >> io - io.seek(0) - return io + pass def fileof(val): - """Converter for passing configs. + """Converter for passing configs or other 'large' data. Handles lists, files directly. Assumes a string is a file name and passes its contents. """ if isinstance(val, types.ListType): - return sxprio(val) + return sxp.to_string(val) if isinstance(val, types.StringType): return file(val) if hasattr(val, 'readlines'): return val + raise XendError('cannot convert value') # todo: need to sort of what urls/paths are using for objects. # e.g. for domains at the moment return '0'. @@ -66,98 +53,253 @@ def fileof(val): # maps /xend/domain/0 to http://wray-m-3.hpl.hp.com:8000/xend/domain/0 # And should accept urls for ids? -def urljoin(location, root, prefix='', rest=''): - prefix = str(prefix) - rest = str(rest) - base = 'http://' + location + root + prefix - url = urlparse.urljoin(base, rest) - return url +class URL: + """A URL. + """ -def nodeurl(location, root, id=''): - return urljoin(location, root, 'node/', id) + def __init__(self, proto='http', host='localhost', port=None, path='', query=None, frag=None): + self.proto = proto + self.host = host + if port: port = int(port) + self.port = port + self.path = path + self.query = query + self.frag = frag + + def url(self): + """Get the full URL string including protocol, location and the full path. + """ + return self.proto + '://' + self.location() + self.fullpath() -def domainurl(location, root, id=''): - return urljoin(location, root, 'domain/', id) + def location(self): + """Get the location part of the URL, including host and port, if present. + """ + if self.port: + return self.host + ':' + str(self.port) + else: + return self.host -def consoleurl(location, root, id=''): - return urljoin(location, root, 'console/', id) + def fullpath(self): + """Get the full path part of the URL, including query and fragment if present. + """ + u = [ self.path ] + if self.query: + u.append('?') + u.append(self.query) + if self.frag: + u.append('#') + u.append(self.frag) + return ''.join(u) + + def relative(self, path='', query=None, frag=None): + """Create a URL relative to this one. + """ + return URL(proto=self.proto, + host=self.host, + port=self.port, + path=self.path + path, + query=query, + frag=frag) + +class XendRequest: + """A request to xend. + """ -def deviceurl(location, root, id=''): - return urljoin(location, root, 'device/', id) + def __init__(self, url, method, args): + """Create a request. Sets up the headers, argument data, and the + url. -def vneturl(location, root, id=''): - return urljoin(location, root, 'vnet/', id) + @param url: the url to request + @param method: request method, GET or POST + @param args: dict containing request args, if any + """ + if url.proto != 'http': + raise ValueError('Invalid protocol: ' + url.proto) + (hdr, data) = encode_data(args) + if args and method == 'GET': + url.query = data + data = None + if method == "POST" and url.path.endswith('/'): + url.path = url.path[:-1] + + self.headers = hdr + self.data = data + self.url = url + self.method = method + +class XendClientProtocol: + """Abstract class for xend clients. + """ -def eventurl(location, root, id=''): - return urljoin(location, root, 'event/', id) + def xendRequest(self, url, method, args=None): + """Make a request to xend. + Implement in a subclass. -def dmesgurl(location, root, id=''): - return urljoin(location, root, 'node/dmesg/', id) + @param url: xend request url + @param method: http method: POST or GET + @param args: request arguments (dict) + """ + raise NotImplementedError() -def xend_request(url, method, data=None): - """Make a request to xend. + def xendGet(self, url, args=None): + """Make a xend request using HTTP GET. + Requests using GET are usually 'safe' and may be repeated without + nasty side-effects. - url xend request url - method http method: POST or GET - data request argument data (dict) - """ - urlinfo = urlparse.urlparse(url) - (uproto, ulocation, upath, uparam, uquery, ufrag) = urlinfo - if DEBUG: print url, urlinfo - if uproto != 'http': - raise StandardError('Invalid protocol: ' + uproto) - if DEBUG: print '>xend_request', ulocation, upath, method, data - (hdr, args) = encode_data(data) - if data and method == 'GET': - upath += '?' + args - args = None - if method == "POST" and upath.endswith('/'): - upath = upath[:-1] - if DEBUG: print "ulocation=", ulocation, "upath=", upath, "args=", args - #hdr['User-Agent'] = 'Mozilla' - #hdr['Accept'] = 'text/html,text/plain' - conn = httplib.HTTPConnection(ulocation) - #conn.response_class = Foo - if DEBUG: conn.set_debuglevel(1) - conn.request(method, upath, args, hdr) - resp = conn.getresponse() - if DEBUG: print resp.status, resp.reason - if DEBUG: print resp.msg.headers - if resp.status in [ http.NO_CONTENT ]: - return None - if resp.status not in [ http.OK, http.CREATED, http.ACCEPTED ]: - raise XendError(resp.reason) - pin = sxp.Parser() - data = resp.read() - if DEBUG: print "***data" , data - if DEBUG: print "***" - pin.input(data); - pin.input_eof() - conn.close() - val = pin.get_val() - #if isinstance(val, types.ListType) and sxp.name(val) == 'val': - # val = val[1] - if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err': - raise XendError(val[1]) - if DEBUG: print '**val='; sxp.show(val); print - return val - -def xend_get(url, args=None): - """Make a xend request using GET. - Requests using GET are 'safe' and may be repeated without - nasty side-effects. + @param url: xend request url + @param data: request arguments (dict) + """ + return self.xendRequest(url, "GET", args) + + def xendPost(self, url, args): + """Make a xend request using HTTP POST. + Requests using POST potentially cause side-effects, and should + not be repeated unless you really want to repeat the side + effect. + + @param url: xend request url + @param args: request arguments (dict) + """ + return self.xendRequest(url, "POST", args) + + def handleStatus(self, version, status, message): + """Handle the status returned from the request. + """ + status = int(status) + if status in [ http.NO_CONTENT ]: + return None + if status not in [ http.OK, http.CREATED, http.ACCEPTED ]: + return self.handleException(XendError(message)) + return 'ok' + + def handleResponse(self, data): + """Handle the data returned in response to the request. + """ + if data is None: return None + try: + pin = sxp.Parser() + pin.input(data); + pin.input_eof() + val = pin.get_val() + except sxp.ParseError, err: + return self.handleException(err) + if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err': + err = XendError(val[1]) + return self.handleException(err) + return val + + def handleException(self, err): + """Handle an exception during the request. + May be overridden in a subclass. + """ + raise err + +class SynchXendClientProtocol(XendClientProtocol): + """A synchronous xend client. This will make a request, wait for + the reply and return the result. """ - return xend_request(url, "GET", args) -def xend_call(url, data): - """Make xend request using POST. - Requests using POST potentially cause side-effects and should - not be repeated unless it really is wanted to do the side - effect again. + def xendRequest(self, url, method, args=None): + """Make a request to xend. + + @param url: xend request url + @param method: http method: POST or GET + @param args: request arguments (dict) + """ + self.request = XendRequest(url, method, args) + conn = httplib.HTTPConnection(url.location()) + if DEBUG: conn.set_debuglevel(1) + conn.request(method, url.fullpath(), self.request.data, self.request.headers) + resp = conn.getresponse() + val = self.handleStatus(resp.version, resp.status, resp.reason) + if val is None: + data = None + else: + data = resp.read() + conn.close() + val = self.handleResponse(data) + return val + +class AsynchXendClient(http.HTTPClient): + """A subclass of twisted's HTTPClient to deal with a connection to xend. + Makes the request when connected, and delegates handling responses etc. + to its protocol (usually an AsynchXendClientProtocol instance). + """ + def __init__(self, protocol, request): + self.protocol = protocol + self.request = request + + def connectionMade(self): + request = self.request + url = self.request.url + self.sendCommand(request.method, url.fullpath()) + self.sendHeader('Host', url.location()) + for (k, v) in request.headers.items(): + self.sendHeader(k, v) + if request.data: + self.sendHeader('Content-Length', len(request.data)) + self.endHeaders() + if request.data: + self.transport.write(request.data) + + def handleStatus(self, version, status, message): + return self.protocol.handleStatus(version, status, message) + + def handleResponse(self, data): + return self.protocol.handleResponse(data) + +class AsynchXendClientProtocol(XendClientProtocol): + """An asynchronous xend client. Uses twisted to connect to xend + and make the request. It does not block waiting for the result, + but sets up a deferred that is called when the result becomes available. + + Uses AsynchXendClient to manage the connection. """ - return xend_request(url, "POST", data) + + def __init__(self): + self.err = None + + def xendRequest(self, url, method, args=None): + """Make a request to xend. The returned deferred is called when + the result is available. + + @param url: xend request url + @param method: http method: POST or GET + @param args: request arguments (dict) + @return: deferred + """ + request = XendRequest(url, method, args) + self.deferred = Deferred() + clientCreator = ClientCreator(reactor, AsynchXendClient, self, request) + clientCreator.connectTCP(url.host, url.port) + return self.deferred + + def callErrback(self, err): + if not self.deferred.called: + self.err = err + self.deferred.errback(err) + return err + + def callCallback(self, val): + if not self.deferred.called: + self.deferred.callback(val) + return val + + def handleException(self, err): + return self.callErrback(err) + + def handleResponse(self, data): + if self.err: return self.err + val = XendClientProtocol.handleResponse(self, data) + if isinstance(val, Exception): + self.callErrback(val) + else: + self.callCallback(val) + return val class Xend: + """Client interface to Xend. + """ """Default location of the xend server.""" SRV_DEFAULT = "localhost:8000" @@ -165,222 +307,288 @@ class Xend: """Default path to the xend root on the server.""" ROOT_DEFAULT = "/xend/" - def __init__(self, srv=None, root=None): + def __init__(self, client=None, srv=None, root=None): + """Create a xend client interface. + If the client protocol is not specified, the default + is to use a synchronous protocol. + + @param client: client protocol to use + @param srv: server host, and optional port (format host:port) + @param root: xend root path on the server + """ + if client is None: + client = SynchXendClientProtocol() + self.client = client self.bind(srv, root) def bind(self, srv=None, root=None): """Bind to a given server. - srv server location (host:port) - root server xend root path + @param srv: server location (host:port) + @param root: xend root path on the server """ if srv is None: srv = self.SRV_DEFAULT if root is None: root = self.ROOT_DEFAULT if not root.endswith('/'): root += '/' - self.location = srv - self.root = root + (host, port) = srv.split(':', 1) + self.url = URL(host=host, port=port, path=root) + + def xendGet(self, url, args=None): + return self.client.xendGet(url, args) + + def xendPost(self, url, data): + return self.client.xendPost(url, data) def nodeurl(self, id=''): - return nodeurl(self.location, self.root, id) + return self.url.relative('node/' + str(id)) def domainurl(self, id=''): - return domainurl(self.location, self.root, id) + return self.url.relative('domain/' + str(id)) def consoleurl(self, id=''): - return consoleurl(self.location, self.root, id) + return self.url.relative('console/' + str(id)) def deviceurl(self, id=''): - return deviceurl(self.location, self.root, id) + return self.url.relative('device/' + str(id)) def vneturl(self, id=''): - return vneturl(self.location, self.root, id) + return self.url.relative('vnet/' + str(id)) def eventurl(self, id=''): - return eventurl(self.location, self.root, id) + return self.url.relative('event/' + str(id)) def dmesgurl(self, id=''): - return dmesgurl(self.location, self.root, id) + return self.url.relative('node/dmesg/' + str(id)) def xend(self): - return xend_get(urljoin(self.location, self.root)) + return self.xendGet(self.url) def xend_node(self): - return xend_get(self.nodeurl()) + return self.xendGet(self.nodeurl()) + + def xend_node_dmesg(self): + return self.xendGet(self.dmesgurl()) def xend_node_cpu_rrobin_slice_set(self, slice): - return xend_call(self.nodeurl(), - {'op' : 'cpu_rrobin_slice_set', - 'slice' : slice }) - + return self.xendPost(self.nodeurl(), + {'op' : 'cpu_rrobin_slice_set', + 'slice' : slice }) + def xend_node_cpu_bvt_slice_set(self, ctx_allow): - return xend_call(self.nodeurl(), - {'op' : 'cpu_bvt_slice_set', - 'ctx_allow' : ctx_allow }) - + return self.xendPost(self.nodeurl(), + {'op' : 'cpu_bvt_slice_set', + 'ctx_allow' : ctx_allow }) + def xend_node_cpu_fbvt_slice_set(self, ctx_allow): - return xend_call(self.nodeurl(), - {'op' : 'cpu_fbvt_slice_set', - 'ctx_allow' : ctx_allow }) + return self.xendPost(self.nodeurl(), + {'op' : 'cpu_fbvt_slice_set', + 'ctx_allow' : ctx_allow }) def xend_domains(self): - return xend_get(self.domainurl()) + return self.xendGet(self.domainurl()) def xend_domain_create(self, conf): - return xend_call(self.domainurl(), - {'op' : 'create', - 'config' : fileof(conf) }) + return self.xendPost(self.domainurl(), + {'op' : 'create', + 'config' : fileof(conf) }) def xend_domain_restore(self, filename): - return xend_call(self.domainurl(), - {'op' : 'restore', - 'file' : filename }) + return self.xendPost(self.domainurl(), + {'op' : 'restore', + 'file' : filename }) - def xend_domain_configure(self, id, config): - return xend_call(self.domainurl(id), - {'op' : 'configure', - 'config' : fileof(conf) }) + def xend_domain_configure(self, id, conf): + return self.xendPost(self.domainurl(id), + {'op' : 'configure', + 'config' : fileof(conf) }) def xend_domain(self, id): - return xend_get(self.domainurl(id)) + return self.xendGet(self.domainurl(id)) def xend_domain_unpause(self, id): - return xend_call(self.domainurl(id), - {'op' : 'unpause' }) + return self.xendPost(self.domainurl(id), + {'op' : 'unpause' }) def xend_domain_pause(self, id): - return xend_call(self.domainurl(id), - {'op' : 'pause' }) + return self.xendPost(self.domainurl(id), + {'op' : 'pause' }) def xend_domain_shutdown(self, id, reason): - return xend_call(self.domainurl(id), - {'op' : 'shutdown', - 'reason' : reason }) + return self.xendPost(self.domainurl(id), + {'op' : 'shutdown', + 'reason' : reason }) def xend_domain_destroy(self, id, reason): - return xend_call(self.domainurl(id), - {'op' : 'destroy', - 'reason' : reason }) + return self.xendPost(self.domainurl(id), + {'op' : 'destroy', + 'reason' : reason }) def xend_domain_save(self, id, filename): - return xend_call(self.domainurl(id), - {'op' : 'save', - 'file' : filename }) + return self.xendPost(self.domainurl(id), + {'op' : 'save', + 'file' : filename }) def xend_domain_migrate(self, id, dst): - return xend_call(self.domainurl(id), - {'op' : 'migrate', - 'destination': dst }) + return self.xendPost(self.domainurl(id), + {'op' : 'migrate', + 'destination': dst }) def xend_domain_pincpu(self, id, cpu): - return xend_call(self.domainurl(id), - {'op' : 'pincpu', - 'cpu' : cpu }) + return self.xendPost(self.domainurl(id), + {'op' : 'pincpu', + 'cpu' : cpu }) def xend_domain_cpu_bvt_set(self, id, mcuadv, warp, warpl, warpu): - return xend_call(self.domainurl(id), - {'op' : 'cpu_bvt_set', - 'mcuadv' : mcuadv, - 'warp' : warp, - 'warpl' : warpl, - 'warpu' : warpu }) - + return self.xendPost(self.domainurl(id), + {'op' : 'cpu_bvt_set', + 'mcuadv' : mcuadv, + 'warp' : warp, + 'warpl' : warpl, + 'warpu' : warpu }) + def xend_domain_cpu_fbvt_set(self, id, mcuadv, warp, warpl, warpu): - return xend_call(self.domainurl(id), - {'op' : 'cpu_fbvt_set', - 'mcuadv' : mcuadv, - 'warp' : warp, - 'warpl' : warpl, - 'warpu' : warpu }) + return self.xendPost(self.domainurl(id), + {'op' : 'cpu_fbvt_set', + 'mcuadv' : mcuadv, + 'warp' : warp, + 'warpl' : warpl, + 'warpu' : warpu }) def xend_domain_cpu_atropos_set(self, id, period, slice, latency, xtratime): - return xend_call(self.domainurl(id), - {'op' : 'cpu_atropos_set', - 'period' : period, - 'slice' : slice, - 'latency' : latency, - 'xtratime': xtratime }) + return self.xendPost(self.domainurl(id), + {'op' : 'cpu_atropos_set', + 'period' : period, + 'slice' : slice, + 'latency' : latency, + 'xtratime': xtratime }) def xend_domain_vifs(self, id): - return xend_get(self.domainurl(id), - { 'op' : 'vifs' }) - + return self.xendGet(self.domainurl(id), + { 'op' : 'vifs' }) + def xend_domain_vif(self, id, vif): - return xend_get(self.domainurl(id), - { 'op' : 'vif', - 'vif' : vif }) - + return self.xendGet(self.domainurl(id), + { 'op' : 'vif', + 'vif' : vif }) + def xend_domain_vbds(self, id): - return xend_get(self.domainurl(id), - {'op' : 'vbds'}) + return self.xendGet(self.domainurl(id), + {'op' : 'vbds'}) def xend_domain_vbd(self, id, vbd): - return xend_get(self.domainurl(id), - {'op' : 'vbd', - 'vbd' : vbd }) + return self.xendGet(self.domainurl(id), + {'op' : 'vbd', + 'vbd' : vbd }) def xend_domain_device_create(self, id, config): - return xend_call(self.domainurl(id), - {'op' : 'device_create', - 'config' : fileof(config) }) + return self.xendPost(self.domainurl(id), + {'op' : 'device_create', + 'config' : fileof(config) }) def xend_domain_device_destroy(self, id, type, idx): - return xend_call(self.domainurl(id), - {'op' : 'device_destroy', - 'type' : type, - 'index' : idx }) + return self.xendPost(self.domainurl(id), + {'op' : 'device_destroy', + 'type' : type, + 'index' : idx }) def xend_consoles(self): - return xend_get(self.consoleurl()) + return self.xendGet(self.consoleurl()) def xend_console(self, id): - return xend_get(self.consoleurl(id)) + return self.xendGet(self.consoleurl(id)) def xend_vnets(self): - return xend_get(self.vneturl()) + return self.xendGet(self.vneturl()) def xend_vnet_create(self, conf): - return xend_call(self.vneturl(), - {'op': 'create', 'config': fileof(conf) }) + return self.xendPost(self.vneturl(), + {'op' : 'create', + 'config' : fileof(conf) }) def xend_vnet(self, id): - return xend_get(self.vneturl(id)) + return self.xendGet(self.vneturl(id)) def xend_vnet_delete(self, id): - return xend_call(self.vneturl(id), - {'op': 'delete' }) + return self.xendPost(self.vneturl(id), + {'op' : 'delete' }) def xend_event_inject(self, sxpr): - val = xend_call(self.eventurl(), - {'op': 'inject', 'event': fileof(sxpr) }) + val = self.xendPost(self.eventurl(), + {'op' : 'inject', + 'event' : fileof(sxpr) }) def xend_dmesg(self): - return xend_get(self.dmesgurl()) - + return self.xendGet(self.dmesgurl()) + + +def xendmain(srv, asynch, fn, args): + if asynch: + client = AsynchXendClientProtocol() + else: + client = None + xend = Xend(srv=srv, client=client) + xend.rc = 0 + try: + v = getattr(xend, fn)(*args) + except XendError, err: + print 'ERROR:', err + return 1 + if asynch: + def cbok(val): + PrettyPrint.prettyprint(val) + reactor.stop() + def cberr(err): + print 'ERROR:', err + xend.rc = 1 + reactor.stop() + v.addCallback(cbok) + v.addErrback(cberr) + reactor.run() + return xend.rc + else: + PrettyPrint.prettyprint(v) + return 0 def main(argv): """Call an API function: - + python XendClient.py fn args... The leading 'xend_' on the function can be omitted. Example: - > python XendClient.py domains - (domain 0 8) - > python XendClient.py domain 0 +python XendClient.py domains + (0 8) +python XendClient.py domain 0 (domain (id 0) (name Domain-0) (memory 128)) """ - server = Xend() - fn = argv[1] + global DEBUG + from getopt import getopt + short_options = 'x:ad' + long_options = ['xend=', 'asynch', 'debug'] + (options, args) = getopt(argv[1:], short_options, long_options) + srv = None + asynch = 0 + for k, v in options: + if k in ['-x', '--xend']: + srv = v + elif k in ['-a', '--asynch']: + asynch = 1 + elif k in ['-d', '--debug']: + DEBUG = 1 + if len(args): + fn = args[0] + args = args[1:] + else: + fn = 'xend' + args = [] if not fn.startswith('xend'): fn = 'xend_' + fn - args = argv[2:] - val = getattr(server, fn)(*args) - PrettyPrint.prettyprint(val) - print + sys.exit(xendmain(srv, asynch, fn, args)) if __name__ == "__main__": main(sys.argv) else: server = Xend() + aserver = Xend( AsynchXendClientProtocol() ) -- 2.30.2